Skip to content

[ty] Use never-change durability for one-shot checks#26359

Merged
MichaReiser merged 4 commits into
mainfrom
micha/one-shot-never-change-inputs
Jun 26, 2026
Merged

[ty] Use never-change durability for one-shot checks#26359
MichaReiser merged 4 commits into
mainfrom
micha/one-shot-never-change-inputs

Conversation

@MichaReiser

@MichaReiser MichaReiser commented Jun 25, 2026

Copy link
Copy Markdown
Member

Summary

Use Salsa's NEVER_CHANGE durability for program, project, and newly created file inputs during one-shot ty check runs. This avoids recording dependencies that we never need, because we'll never perform an incremental check.

We can only do this for check, but not for fixes (we need to recheck after applying fixes), watch mode, and the server.

I decided to leave existing benchmarks unchanged (they don't use freeze), to ensure we measure the "worst-case" mode. I added one freeze specific benchmark, with the hope that it will catch any frequently read input that we fail to freeze.

I'm not a huge fan of these kind of optimizations, but the numbers are compelling enough, where I think this is justified. There's one caveat to this: We won't get away with this if we ever decide to enable persistent caching. I think that's fine. If we add persistent caching, the perf win should pay for the extra 10% cost for retaining dependencies.

Test Plan

Benchmarks when enabling freezing (now disabled again)

Screenshot 2026-06-26 at 12 00 19

Memory report when enabling freezing

Project Old New Diff Outcome
flake8 28.96MB 27.35MB -5.55% (1.61MB) ⬇️
trio 70.34MB 62.48MB -11.18% (7.86MB) ⬇️
sphinx 166.69MB 139.01MB -16.60% (27.68MB) ⬇️
prefect 448.74MB 377.77MB -15.81% (70.96MB) ⬇️

Testing: Passed the ty CLI, ruff_db, and ty_project test suites, Clippy, and repository hooks.

@MichaReiser MichaReiser force-pushed the micha/one-shot-never-change-inputs branch from a189517 to 5b1b3af Compare June 25, 2026 09:01
@astral-sh-bot

astral-sh-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 94.47%. The percentage of expected errors that received a diagnostic held steady at 89.19%. The number of fully passing files held steady at 95/134.

@astral-sh-bot

astral-sh-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot

astral-sh-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-argument-type 19 0 1
no-matching-overload 11 0 0
unresolved-attribute 9 0 2
invalid-assignment 2 0 3
type-assertion-failure 0 1 2
unsupported-operator 0 0 3
invalid-return-type 1 1 0
not-iterable 0 0 1
unused-type-ignore-comment 0 1 0
Total 42 3 12

Large timing changes:

Project Old Time New Time Change
isort 0.23s 0.11s -50%
Raw diff (57 changes)
beartype (https://github.com/beartype/beartype)
+ beartype/bite/collection/infercollectionitems.py:288:47 error[invalid-argument-type] Argument to function `make_hint_pep484604_union` is incorrect: Expected `Sequence[TypeForm[Any]]`, found `tuple[object, ...]`
+ beartype/bite/collection/infercollectionitems.py:289:49 error[invalid-argument-type] Argument to function `make_hint_pep484604_union` is incorrect: Expected `Sequence[TypeForm[Any]]`, found `tuple[object, ...]`

core (https://github.com/home-assistant/core)
- homeassistant/components/tasmota/light.py:224:40 error[unsupported-operator] Operator `in` is not supported between objects of type `Literal[ColorMode.HS]` and `set[ColorMode] | None | set[Unknown]`
+ homeassistant/components/tasmota/light.py:224:40 error[unsupported-operator] Operator `in` is not supported between objects of type `Literal[ColorMode.HS]` and `set[ColorMode] | None`
- homeassistant/components/tasmota/light.py:228:37 error[unsupported-operator] Operator `in` is not supported between objects of type `Literal[ColorMode.WHITE]` and `set[ColorMode] | None | set[Unknown]`
+ homeassistant/components/tasmota/light.py:228:37 error[unsupported-operator] Operator `in` is not supported between objects of type `Literal[ColorMode.WHITE]` and `set[ColorMode] | None`
- homeassistant/components/tasmota/light.py:239:17 error[unsupported-operator] Operator `in` is not supported between objects of type `Literal[ColorMode.COLOR_TEMP]` and `set[ColorMode] | None | set[Unknown]`
+ homeassistant/components/tasmota/light.py:239:17 error[unsupported-operator] Operator `in` is not supported between objects of type `Literal[ColorMode.COLOR_TEMP]` and `set[ColorMode] | None`

cwltool (https://github.com/common-workflow-language/cwltool)
+ cwltool/main.py:244:57 error[no-matching-overload] No overload of bound method `str.join` matches arguments
+ cwltool/validate_js.py:101:29 error[invalid-argument-type] Method `__getitem__` of type `bound method Top[MutableMapping[Unknown, Unknown]].__getitem__(key: Never, /) -> object` cannot be called with key of type `str` on object of type `Top[MutableMapping[Unknown, Unknown]]`
+ cwltool/validate_js.py:101:29 error[invalid-argument-type] Method `__getitem__` of type `bound method str.__getitem__(key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> str` cannot be called with key of type `str` on object of type `str`

egglog-python (https://github.com/egraphs-good/egglog-python)
- python/egglog/egraph_state.py:378:25 error[type-assertion-failure] Type `Unknown & ~Literal["delete"] & ~Literal["subsume"]` is not equivalent to `Never`
- python/egglog/egraph_state.py:623:25 error[type-assertion-failure] Type `Unknown & ~None` is not equivalent to `Never`
+ python/egglog/egraph_state.py:623:25 error[type-assertion-failure] Type `Value` is not equivalent to `Never`
- python/egglog/pretty.py:309:17 error[type-assertion-failure] Type `Unknown & ~None` is not equivalent to `Never`
+ python/egglog/pretty.py:309:17 error[type-assertion-failure] Type `Value` is not equivalent to `Never`
+ python/egglog/thunk.py:69:28 error[invalid-return-type] Return type does not match returned value: expected `T@__call__`, found `TypeVar`

freqtrade (https://github.com/freqtrade/freqtrade)
- freqtrade/optimize/analysis/lookahead.py:137:57 error[invalid-argument-type] Argument to function `LookaheadAnalysis.get_result` is incorrect: Expected `DataFrame`, found `dict[Unknown, Unknown]`
+ freqtrade/optimize/analysis/lookahead.py:137:57 error[invalid-argument-type] Argument to function `LookaheadAnalysis.get_result` is incorrect: Expected `DataFrame`, found `dict[str, DataFrame]`

jax (https://github.com/google/jax)
- jax/experimental/mosaic/gpu/constraints.py:211:6 error[invalid-return-type] Function can implicitly return `None`, which is not assignable to return type `Variable | RegisterLayout | TMEMLayout | ... omitted 6 union elements`

manticore (https://github.com/trailofbits/manticore)
+ tests/other/test_smtlibv2.py:139:25 error[invalid-argument-type] Argument to bound method `set.add` is incorrect: Expected `<class 'Expression'> | <class 'BoolOperation'> | <class 'BitVecOperation'> | ... omitted 4 union elements`, found `type`

mongo-python-driver (https://github.com/mongodb/mongo-python-driver)
+ pymongo/asynchronous/mongo_client.py:892:37 error[invalid-argument-type] Argument to bound method `AsyncMongoClient._init_based_on_options` is incorrect: Expected `Collection[tuple[str, int]]`, found `set[Unknown | tuple[str, int | None]] & ~AlwaysFalsy`
+ pymongo/asynchronous/mongo_client.py:971:41 error[invalid-argument-type] Argument to bound method `AsyncMongoClient._init_based_on_options` is incorrect: Expected `Collection[tuple[str, int]]`, found `set[Unknown | tuple[str, int | None]] & ~AlwaysFalsy`
+ pymongo/synchronous/mongo_client.py:893:37 error[invalid-argument-type] Argument to bound method `MongoClient._init_based_on_options` is incorrect: Expected `Collection[tuple[str, int]]`, found `set[Unknown | tuple[str, int | None]] & ~AlwaysFalsy`
+ pymongo/synchronous/mongo_client.py:972:41 error[invalid-argument-type] Argument to bound method `MongoClient._init_based_on_options` is incorrect: Expected `Collection[tuple[str, int]]`, found `set[Unknown | tuple[str, int | None]] & ~AlwaysFalsy`

pandas (https://github.com/pandas-dev/pandas)
+ pandas/core/internals/construction.py:430:21 error[invalid-argument-type] Argument to function `construct_1d_arraylike_from_scalar` is incorrect: Expected `str | bytes | date | ... omitted 10 union elements`, found `object`
+ pandas/core/internals/construction.py:434:17 error[invalid-assignment] Invalid subscript assignment with key of type `int` and value of type `ExtensionArray | ndarray[tuple[Any, ...], dtype[Any]]` on object of type `list[int | float]`

pylint (https://github.com/pycqa/pylint)
+ pylint/checkers/match_statements_checker.py:164:25 error[unresolved-attribute] Attribute `value` is not defined on `NodeNG` in union `NodeNG | Proxy`
+ pylint/checkers/classes/class_checker.py:946:54 error[unresolved-attribute] Attribute `name` is not defined on `NodeNG` in union `NodeNG | UninferableBase | Proxy`
+ pylint/checkers/refactoring/recommendation_checker.py:261:29 error[unresolved-attribute] Attribute `name` is not defined on `Attribute` in union `Attribute | Name`
+ pylint/checkers/refactoring/recommendation_checker.py:265:29 error[unresolved-attribute] Attribute `attrname` is not defined on `Name` in union `Attribute | Name`
+ pylint/checkers/refactoring/refactoring_checker.py:652:16 error[unresolved-attribute] Object of type `NodeNG | None` has no attribute `value`
+ pylint/checkers/refactoring/refactoring_checker.py:1178:12 error[unresolved-attribute] Object of type `NodeNG` has no attribute `name`
+ pylint/checkers/refactoring/refactoring_checker.py:1830:32 error[unresolved-attribute] Attribute `name` is not defined on `NodeNG` in union `NodeNG | Proxy`
+ pylint/checkers/refactoring/refactoring_checker.py:1841:38 error[unresolved-attribute] Attribute `name` is not defined on `NodeNG` in union `NodeNG | Proxy`
+ pylint/checkers/utils.py:1303:83 error[invalid-argument-type] Argument to function `has_known_bases` is incorrect: Expected `ClassDef`, found `(ClassDef & BaseInstance) | (FunctionDef & BaseInstance) | (Lambda & BaseInstance) | (UnboundMethod & BaseInstance)`
+ pylint/checkers/utils.py:1304:38 error[invalid-argument-type] Argument is incorrect: Expected `NodeNG`, found `(ClassDef & BaseInstance) | (FunctionDef & BaseInstance) | (Lambda & BaseInstance) | (UnboundMethod & BaseInstance)`

pyppeteer (https://github.com/pyppeteer/pyppeteer)
- pyppeteer/launcher.py:148:40 warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive

pytest (https://github.com/pytest-dev/pytest)
- src/_pytest/assertion/rewrite.py:1012:25 error[invalid-assignment] Invalid subscript assignment with key of type `Unknown` and value of type `expr` on object of type `dict[str, str]`
+ src/_pytest/assertion/rewrite.py:1012:25 error[invalid-assignment] Invalid subscript assignment with key of type `str` and value of type `expr` on object of type `dict[str, str]`
- src/_pytest/assertion/rewrite.py:1110:17 error[invalid-assignment] Invalid subscript assignment with key of type `Unknown` and value of type `NamedExpr` on object of type `dict[str, str]`
+ src/_pytest/assertion/rewrite.py:1110:17 error[invalid-assignment] Invalid subscript assignment with key of type `str` and value of type `NamedExpr` on object of type `dict[str, str]`
- src/_pytest/assertion/rewrite.py:1128:21 error[invalid-assignment] Invalid subscript assignment with key of type `Unknown` and value of type `NamedExpr` on object of type `dict[str, str]`
+ src/_pytest/assertion/rewrite.py:1128:21 error[invalid-assignment] Invalid subscript assignment with key of type `Any & str` and value of type `NamedExpr` on object of type `dict[str, str]`

schema_salad (https://github.com/common-workflow-language/schema_salad)
+ src/schema_salad/typescript_codegen.py:408:25 error[invalid-argument-type] Argument is incorrect: Expected `bool`, found `Any | str | Literal[False]`

scikit-learn (https://github.com/scikit-learn/scikit-learn)
+ sklearn/linear_model/_logistic.py:2402:30 error[no-matching-overload] No overload of function `tile` matches arguments
- sklearn/linear_model/tests/test_logistic.py:1940:12 error[unresolved-attribute] Attribute `shape` is not defined on `list[Unknown]`, `int`, `float` in union `list[Unknown] | ndarray[tuple[Any, ...], dtype[Unknown]] | int | float`
+ sklearn/linear_model/tests/test_logistic.py:1940:12 error[unresolved-attribute] Attribute `shape` is not defined on `list[Any]`, `int`, `float` in union `list[Any] | ndarray[tuple[Any, ...], dtype[Unknown]] | int | float`
- sklearn/linear_model/tests/test_logistic.py:1941:12 error[unresolved-attribute] Attribute `shape` is not defined on `list[Unknown]`, `int`, `float` in union `list[Unknown] | ndarray[tuple[Any, ...], dtype[Unknown]] | int | float`
+ sklearn/linear_model/tests/test_logistic.py:1941:12 error[unresolved-attribute] Attribute `shape` is not defined on `list[Unknown | str | None | int | float]`, `int`, `float` in union `list[Unknown | str | None | int | float] | Unknown | ndarray[tuple[Any, ...], dtype[Any]] | int | float`
+ sklearn/linear_model/tests/test_logistic.py:1946:9 error[no-matching-overload] No overload of function `assert_allclose` matches arguments
+ sklearn/linear_model/tests/test_logistic.py:1995:5 error[no-matching-overload] No overload of function `assert_allclose` matches arguments
+ sklearn/metrics/tests/test_common.py:1305:13 error[invalid-assignment] Object of type `str` is not assignable to attribute `__name__` on type `(Unknown & Top[partial[Unknown]]) | partial[(...) -> Unknown] | (((...) -> Unknown) & Top[partial[Unknown]])`

scipy (https://github.com/scipy/scipy)
+ scipy/conftest.py:661:55 error[invalid-argument-type] Argument to function `filterwarnings` is incorrect: Expected `bool`, found `<class 'RuntimeWarning'> | str | <class 'DeprecationWarning'> | ... omitted 3 union elements`
+ scipy/conftest.py:661:55 error[invalid-argument-type] Argument to function `filterwarnings` is incorrect: Expected `int`, found `<class 'RuntimeWarning'> | str | <class 'DeprecationWarning'> | ... omitted 3 union elements`
+ scipy/conftest.py:661:55 error[invalid-argument-type] Argument to function `filterwarnings` is incorrect: Expected `str`, found `<class 'RuntimeWarning'> | str | <class 'DeprecationWarning'> | ... omitted 3 union elements`
+ scipy/conftest.py:661:55 error[invalid-argument-type] Argument to function `filterwarnings` is incorrect: Expected `str`, found `<class 'RuntimeWarning'> | str | <class 'DeprecationWarning'> | ... omitted 3 union elements`
+ scipy/conftest.py:661:55 error[invalid-argument-type] Argument to function `filterwarnings` is incorrect: Expected `type[Warning]`, found `<class 'RuntimeWarning'> | str | <class 'DeprecationWarning'> | ... omitted 3 union elements`
+ scipy/spatial/tests/test_distance.py:615:14 error[no-matching-overload] No overload of function `cdist` matches arguments
+ scipy/spatial/tests/test_distance.py:664:14 error[no-matching-overload] No overload of function `cdist` matches arguments
+ scipy/spatial/tests/test_distance.py:665:14 error[no-matching-overload] No overload of function `cdist` matches arguments
+ scipy/spatial/tests/test_distance.py:677:15 error[no-matching-overload] No overload of function `cdist` matches arguments
+ scipy/spatial/tests/test_distance.py:1394:19 error[no-matching-overload] No overload of function `pdist` matches arguments
+ scipy/spatial/tests/test_distance.py:1432:14 error[no-matching-overload] No overload of function `pdist` matches arguments
+ scipy/spatial/tests/test_distance.py:1433:14 error[no-matching-overload] No overload of function `pdist` matches arguments

sympy (https://github.com/sympy/sympy)
+ sympy/simplify/radsimp.py:206:35 error[unresolved-attribute] Attribute `base` is not defined on `Expr` in union `Expr | Unknown`
- sympy/simplify/radsimp.py:708:28 error[not-iterable] Object of type `set[Unknown] | Basic` may not be iterable
+ sympy/simplify/radsimp.py:708:28 error[not-iterable] Object of type `set[Expr] | Basic` may not be iterable

typeshed-stats (https://github.com/AlexWaygood/typeshed-stats)
+ src/typeshed_stats/gather.py:520:27 error[invalid-argument-type] Argument to function `sorted` is incorrect: Expected `Iterable[str]`, found `Top[list[Unknown]]`

Full report with detailed diff (timing results)

@astral-sh-bot

astral-sh-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@codspeed-hq

codspeed-hq Bot commented Jun 25, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 145 untouched benchmarks
🆕 2 new benchmarks
⏩ 4 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
🆕 Simulation DateType (frozen inputs) N/A 247.1 ms N/A
🆕 Memory DateType (frozen inputs) N/A 17.1 MB N/A

Comparing micha/one-shot-never-change-inputs (9134676) with main (4398bd3)

Open in CodSpeed

Footnotes

  1. 4 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@MichaReiser MichaReiser added performance Potential performance improvement ty Multi-file analysis & type inference labels Jun 25, 2026
@MichaReiser MichaReiser force-pushed the micha/one-shot-never-change-inputs branch from 59da53b to 6dbe4ff Compare June 26, 2026 10:09
@MichaReiser MichaReiser marked this pull request as ready for review June 26, 2026 10:53
@MichaReiser MichaReiser requested review from a team as code owners June 26, 2026 10:53
@astral-sh-bot astral-sh-bot Bot requested a review from charliermarsh June 26, 2026 10:53
Comment thread crates/ty/src/lib.rs
// they must keep the inputs mutable.
if !watch && matches!(mode, MainLoopMode::Check) && memory_report.is_none() {
db.freeze();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we couple this with ruff_db::disable_lru(&mut db);? Like, should we make the LRU disablement conditional on the same things, or not necessary?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could move the two checks closer together, but I wouldn't unify them. We don't need LRU for --fix, because it's still a short lived check. We can also use it when running the memory report.

@MichaReiser MichaReiser enabled auto-merge (squash) June 26, 2026 11:14
@MichaReiser MichaReiser merged commit 7ec72af into main Jun 26, 2026
62 checks passed
@MichaReiser MichaReiser deleted the micha/one-shot-never-change-inputs branch June 26, 2026 11:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Potential performance improvement ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants